package com.xl.utils.util;

/**
 * @author xionglin
 * @date 2021/1/20 22:13
 * @Description 杰拉德相似系数工具类，推荐算法（协同过滤算法工具类）
 */

import com.xl.utils.util.bo.SimilarityAndDifferentSetBO;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 杰卡德(Jaccard)相似度计算，相似度越大越接近
 * 判定方式：Jaccard相似性系数（Jaccard similarity coefficient） ，通过计算两个集合交集的数量除以并集的数量来评估他们的相似度
 */
public class JaccardSimilarityUtil {

    private JaccardSimilarityUtil() {
    }

    /**
     * 简单的杰卡德(Jaccard)相似度计算   不考虑评分等 ；  分析当前用户的行为数据集和某个用户的行为数据集，返回两个数据集的杰卡德(Jaccard)相似度计算，
     *
     * @param currentUserData 比如当前用户购买的商品id的set集合
     * @param randomUsersData 比如某个用户购买的商品id的set集合
     * @return
     */
    public static double getJaccardSimilarity(HashSet currentUserData, HashSet randomUsersData) {
        if (currentUserData.isEmpty()) {
            //说明当前用户未发生行为数据（比如说x每购买过商品。。。。）无法知道用户的喜好
            return 0d;
        }
        if (randomUsersData.isEmpty()) {
            //被分析用户未发生行为数据（比如说每购买过商品。。。。）说明被分析用户和当前用户相似度未0
            return 0d;
        }
        //两个数据集的并集数量  Jaccard相似性系数公式的分母
        HashSet<Object> unionSet = new HashSet<>();
        unionSet.addAll(currentUserData);
        unionSet.addAll(randomUsersData);
        double unionNumber = (double) unionSet.size();

        //两个数据集的交集数量  Jaccard相似性系数公式的分子
        HashSet<Object> intersectionet = new HashSet<>();
        intersectionet.addAll(currentUserData);
        intersectionet.retainAll(randomUsersData);
        double intersectionNumber = (double) intersectionet.size();

        //两个数据集的杰卡德(Jaccard)相似度
        double jaccardSimilarity = intersectionNumber / unionNumber;
        return jaccardSimilarity;
    }

    /**
     * 求randomUsersData中存在而在currentUserData不存在的数据，相当于求rrandomUsersData对于currentUserData的差级，作为被推送的行为数据
     *
     * @param currentUserData 比如当前用户购买的商品id的set集合
     * @param randomUsersData 比如某个用户购买的商品id的set集合
     * @return
     */
    public static HashSet getDifferentSet(HashSet currentUserData, HashSet randomUsersData) {
        if (currentUserData.isEmpty()||randomUsersData.isEmpty()) {
            //返回空set
            return  new HashSet();
        }
        //求randomUsersData对于currentUserData差集
        HashSet<Object> result = new HashSet<>();
        result.addAll(randomUsersData);
        result.removeAll(currentUserData);
        return result;
    }

    /**
     * 返回两数据集的杰卡德(Jaccard)相似度计算，且返回randomUsersData对于currentUserData的差集合
     *
     * @param currentUserData 比如当前用户购买的商品id的set集合
     * @param randomUsersData 比如某个用户购买的商品id的set集合
     * @return
     */
    public static SimilarityAndDifferentSetBO getJaccardSimilarityAndDifferentSet(HashSet currentUserData, HashSet randomUsersData) {
        double jaccardSimilarity = getJaccardSimilarity(currentUserData, randomUsersData);
        HashSet differentSet = getDifferentSet(currentUserData, randomUsersData);
        SimilarityAndDifferentSetBO jaccardSimilarityAndDifferentSetBO = new SimilarityAndDifferentSetBO();
        jaccardSimilarityAndDifferentSetBO.setDifferentSet(differentSet);
        jaccardSimilarityAndDifferentSetBO.setSimilarity(jaccardSimilarity);
        return jaccardSimilarityAndDifferentSetBO;
    }


    /**
     * 开发中直接调用这个方法，反回的记过就是按杰卡德(Jaccard)相似度从高到底排序的数据，而且待用需要推荐的数据
     * 返回两数据集的杰卡德(Jaccard)相似度计算，且返回randomUsersData对于currentUserData的差集合
     *
     * @param currentUserData     比如当前用户购买的商品id的set集合
     * @param randomUsersDataList 除了当前用户以外，其他所以用户购买的   商品id的set集合的List   ，list里是每个用户的购买商品id的set
     * @return
     */
    public static List<SimilarityAndDifferentSetBO> getJaccardSimilarityAndDifferentSet(HashSet currentUserData, List<HashSet> randomUsersDataList) {
        if (currentUserData.isEmpty()) {
            //如果当前用户行为数据为空，返回空list
            return new ArrayList<>();
        }
        if (randomUsersDataList.isEmpty()) {
            //系统不存在行为数据无法分析，返回空list
            return new ArrayList<>();
        }
        //返回数据
        ArrayList<SimilarityAndDifferentSetBO> resultInfo = new ArrayList<>();
        //循环处理
        for (HashSet element : randomUsersDataList) {
            resultInfo.add(getJaccardSimilarityAndDifferentSet(currentUserData, element));
        }
        //按杰卡德(Jaccard)相似度降序排序
        List<SimilarityAndDifferentSetBO> result = resultInfo.stream().sorted(Comparator.comparing(SimilarityAndDifferentSetBO::getSimilarity).reversed()).collect(Collectors.toList());
        return result;
    }


    //测试
    public static void main(String[] args) {
        HashSet<Object> set1 = new HashSet<Object>() {
            {
                add("王者荣耀");
                add("英雄联盟");
                add("穿越火线");
                add("地下城与勇士");
            }
        };
        HashSet<Object> set2 = new HashSet<Object>() {
            {
                add("王者荣耀");
                add("英雄联盟");
                add("穿越火线");
                add("梦幻西游");
            }
        };
        ArrayList<HashSet> objects = new ArrayList<>();
        objects.add(set1);
        objects.add(set2);
        HashSet<Object> currentUserSet = new HashSet<Object>() {
            {
                add("王者荣耀");
                add("地下城与勇士");
                add("魔兽世界");
            }
        };

        System.out.println(getJaccardSimilarityAndDifferentSet(currentUserSet,objects));


    }

    /**
     * 集合交集、并集、差集的测试demo
     *
     * @param args
     */
//    public static void main(String[] args) {
//
//        HashSet<Object> result = new HashSet<Object>();
//        HashSet<Object> set1 = new HashSet<Object>() {
//            {
//                add("王者荣耀");
//                add("英雄联盟");
//                add("穿越火线");
//                add("地下城与勇士");
//            }
//        };
//
//        HashSet<Object> set2 = new HashSet<Object>() {
//            {
//                add("王者荣耀");
//                add("地下城与勇士");
//                add("魔兽世界");
//            }
//        };
//
//        //求交集
//        result.clear();
//        result.addAll(set1);
//        result.retainAll(set2);
//        System.out.println("交集：" + result);
//
//
//        //求set1对于set2的差集
//        result.clear();
//        result.addAll(set1);
//        result.removeAll(set2);
//        System.out.println("set1对于set2的差集：" + result);
//
//        //求set2对于set1的差集
//        result.clear();
//        result.addAll(set2);
//        result.removeAll(set1);
//        System.out.println("set2对于set1的差集：" + result);
//
//
//        //求并集
//        result.clear();
//        result.addAll(set1);
//        result.addAll(set2);
//        System.out.println("并集：" + result);
//
//    }


}
